package la.iok.hzsvn.lewin.movie.service;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import la.iok.hzsvn.lewin.movie.config.DfsProperties;
import la.iok.hzsvn.lewin.movie.core.RoleEnum;
import la.iok.hzsvn.lewin.movie.core.Token;
import la.iok.hzsvn.lewin.movie.core.exception.ErrorCode;
import la.iok.hzsvn.lewin.movie.entity.*;
import la.iok.hzsvn.lewin.movie.model.DigestInfo;
import la.iok.hzsvn.lewin.movie.model.ObsSignedUrl;
import la.iok.hzsvn.lewin.movie.obs.ObsService;
import la.iok.hzsvn.lewin.mybatis.mapper.BaseMapper;
import la.iok.hzsvn.lewin.mybatis.model.PageVo;
import la.iok.hzsvn.lewin.mybatis.service.BaseServiceImpl;
import la.iok.hzsvn.share.annotations.NotNull;
import la.iok.hzsvn.share.annotations.Nullable;
import la.iok.hzsvn.share.dfs.exception.DfsException;
import la.iok.hzsvn.share.dfs.model.DfsDescriptor;
import la.iok.hzsvn.share.dfs.model.DfsResult;
import la.iok.hzsvn.share.tools.crypto.Sha1Util;
import la.iok.hzsvn.share.tools.exception.SystemException;
import la.iok.hzsvn.share.utils.ParameterTypeUtils;
import la.iok.hzsvn.share.utils.PathUtils;
import la.iok.hzsvn.share.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;

public class MovieFileServiceImpl<T extends MovieFile,M extends BaseMapper<T>> extends BaseServiceImpl<T,M,Long> implements MovieFileService<T>{
    protected final WebDfsService webDfsService;
    private final DfsProperties dfsProperties;
    @Autowired
    private ObsService obsService;

    public MovieFileServiceImpl(M mapper, WebDfsService webDfsService, DfsProperties dfsProperties) {
        super(mapper);
        this.webDfsService = webDfsService;
        this.dfsProperties = dfsProperties;
    }

    @Override
    public Long save(@NotNull String fileName, @NotNull String path, @NotNull String digest) {
        Class<?> cls = ParameterTypeUtils.getSuperClassGenericType(getClass(),0);
        T entity = null;
        try {
            entity = (T) cls.getConstructor().newInstance();
            entity.setDigest(digest);
            entity.setFileName(fileName);
            entity.setPath(path);
            insertSelective(entity);
            return entity.getId();
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            throw new SystemException("创建实体对象失败",e);
        }
    }

    @Override
    public DfsResult uploadFile(String digest, File file) {
        String path = webDfsService.storePath(digest,file);
        // 上传到分布式文件系统后
        return webDfsService.uploadFile(path,file);
    }

    public DfsResult uploadFile(String digest, String fileName) {
        String path = webDfsService.storePath(digest,fileName);
        // 上传到分布式文件系统后
        return DfsResult.success(webDfsService.dfsFile(path));
    }

    @NotNull
    @Override
    public File transToLocalFile(@NotNull MultipartFile file) {
        // 1、把上传的文件写到本地临时文件
        String ofn = file.getOriginalFilename();
        if(StringUtils.isBlank(ofn)){
            throw new DfsException("文件名称不能为空");
        }
        try {
            String path = StringUtils.isBlank(dfsProperties.getTmpPath()) ? PathUtils.getPath() : dfsProperties.getTmpPath();
            File localTempFile = new File(path, ofn);
            File p = localTempFile.getParentFile();
            if(!p.exists()){
                if(!p.mkdirs()){
                    throw new DfsException("创建临时文件保存目录[" + p.getAbsolutePath() + "]失败");
                }
            }
            file.transferTo(localTempFile);
            return localTempFile;
        } catch (IOException e) {
            throw new DfsException("存储文件到本地失败",e);
        }
    }

    @Override
    public void increment(@Nullable Long id) {
        increment("count",id);
    }

    @Override
    public void decrement(@Nullable Long id) {
        decrement("count",id);
    }

    @Override
    public T select(@Nullable String digest) {
        if(digest == null){
            return null;
        }
        return select("digest",digest);
    }

    @Nullable
    @Override
    public String digest(@Nullable Long id) {
        T entity = select(id);
        if(entity != null && webDfsService.exist(webDfsService.dfsFile(entity.getPath()))){
            return entity.getDigest();
        }
        return null;
    }

    @Nullable
    @Override
    public String storePath(@Nullable Long id) {
        T entity = select(id);
        if(entity != null && webDfsService.exist(webDfsService.dfsFile(entity.getPath()))){
            return entity.getPath();
        }
        return null;
    }

    @Override
    public boolean exist(Long id) {
        T entity = select(id);
        if(entity == null){
            return false;
        }
        return webDfsService.exist(webDfsService.dfsFile(entity.getPath()));
    }

    @Override
    public void downloadFile(@NotNull OutputStream os, @Nullable T entity) {
        if(entity != null){
            DfsDescriptor descriptor = webDfsService.dfsFile(entity.getPath());
            if(webDfsService.exist(descriptor)){
                webDfsService.downloadFile(os, descriptor);
            }
        }
    }

    @Override
    public PageVo<T> freeFiles(int page, int limit, Integer fileDeleted) {
        Page<T> list = PageHelper.startPage(page, limit);
        if(fileDeleted != null){
            executeListSelect("count=0 and fileDeleted=" + fileDeleted);
        }else{
            listByProperty("count",0);
        }
        return page(list, file->{
            file.setPath(null);
            file.setCount(null);
            return file;
        });
    }

    @Override
    public void deleteFreeFile(Token token, Long id) {
        token.validAnyAuthority(RoleEnum.Admin);
        deleteFreeFileWithoutAuthorityCheck(id);
    }

    @Override
    public void deleteFreeFileWithoutAuthorityCheck(Long id) {
        T file = freeFileWithoutReference(id);
        for (Class<?> cls : Arrays.asList(AppFile.class, ClueCardFile.class, MovieCoverFile.class, MovieAssistFile.class, MovieSceneFile.class, PlayRoleBookFile.class, PlayRoleFile.class)){
            if(cls.equals(file.getClass())){
                // 同一张表,不同记录的相同存储路径下的文件,如果其他记录未标记删除,则不能删除对应的文件
                T other = executeSelect("id!="+file.getId()+" and fileDeleted=0 and path='" + file.getPath() + "' limit 1");
                ErrorCode.FILE_REFERENCED_BY_OTHER.assertNull(other,()->new Object[]{
                        String.format("id:%s,path:%s", file.getId(), file.getPath()), String.format("%s(id:%s)", cls.getSimpleName(), other.getId())
                });
                continue;
            }
            List<?> list = executeListSelect(cls,"select * from " + cls.getSimpleName() + " where fileDeleted=0 and path='" + file.getPath() + "' limit 1");
            ErrorCode.FILE_REFERENCED_BY_OTHER.assertEmpty(list,file.getPath(),cls.getSimpleName());
        }
        markFileDeleted(id);
        webDfsService.deleteFile(webDfsService.dfsFile(file.getPath()));
    }

    /**
     * 如果没有其他对象引用文件对象则返回文件对象，否则抛出异常
     * @param id 文件记录id
     * @return 文件记录,此文件记录没有被其他使用文件的对象以id关联引用
     */
    private T freeFileWithoutReference(Long id) {
        T file = select(id);
        ErrorCode.RECORD_NOT_EXIST.assertNotNull(file,()->new Object[]{
                ParameterTypeUtils.getSuperClassGenericType(getClass(),0).getSimpleName()+"(id:" + id + ")"
        });
        ErrorCode.FILE_REFERENCED_BY_COUNT.assertEqual(0,file.getCount(),file.getPath(),file.getClass().getSimpleName(),file.getCount());
        validateReference(file);
        return file;
    }

    @Override
    public void markFreeFileDeleted(@NotNull Token token, Long id) {
        token.validAnyAuthority(RoleEnum.Admin);
        freeFileWithoutReference(id);
        markFileDeleted(id);
    }

    private void markFileDeleted(Long id){
        update(id, "fileDeleted", 1);
    }

    /**
     * 子类验证是否有其他引用
     * @param file 文件对象
     */
    protected void validateReference(@NotNull T file) {
        throw new RuntimeException("需要子类重写");
    }

    @Override
    public Long uploadFile(MultipartFile file) {
        File localTempFile = transToLocalFile(file);
        try{
            return uploadFile(localTempFile);
        }finally {
            localTempFile.delete();
        }
    }

    @Override
    public Long uploadFile(File localTempFile) {
        String digest = Sha1Util.sha1(localTempFile);
        T entity = select(digest);
        if(entity == null){
            DfsResult result = uploadFile(digest, localTempFile);
            if(!result.success()){
                throw new DfsException(result.getMessage());
            }
            Long id = save(localTempFile.getName(),result.getDescriptor().path(),digest);
            update(id,"length",localTempFile.length());
            return id;
        }else{
            if(!webDfsService.exist(webDfsService.dfsFile(entity.getPath()))){
                //文件系统中不存在，那再上次一次文件
                String storePath = StringUtils.isBlank(entity.getPath())?webDfsService.storePath(digest, localTempFile):entity.getPath();
                DfsResult result = webDfsService.uploadFile(storePath, localTempFile);
                if(!result.success()){
                    throw new DfsException(result.getMessage());
                }
                update(entity.getId(),"path",result.getDescriptor().path());
                update(entity.getId(),"fileName", localTempFile.getName());
                update(entity.getId(),"length",localTempFile.length());
            }
            // 更新文件删除标记为未删除
            update(entity.getId(),"fileDeleted",0);
            return entity.getId();
        }
    }

    @Override
    public ObsSignedUrl uploadFile(DigestInfo digest) {
        T entity = select(digest.getSha1());
        if(entity == null){
            DfsResult result = uploadFile(digest.getSha1(),digest.getFileName());
            if(!result.success()){
                throw new DfsException(result.getMessage());
            }
            ObsSignedUrl url = obsService.signUrlPut(result.getDescriptor().path());
            Long fileId = save(digest.getFileName(),result.getDescriptor().path(),digest.getSha1());
            url.setFileId(fileId);
            url.setExist(false);
            return url;
        }else{
            if(!webDfsService.exist(webDfsService.dfsFile(entity.getPath()))){
                //文件系统中不存在，那再上次一次文件
                String storePath = StringUtils.isBlank(entity.getPath())?webDfsService.storePath(digest.getSha1(),digest.getFileName()):entity.getPath();
                ObsSignedUrl url = obsService.signUrlPut(storePath);
                update(entity.getId(),"path",storePath);
                update(entity.getId(),"fileName",digest.getFileName());
                url.setFileId(entity.getId());
                url.setExist(false);
                return url;
            }else{
                ObsSignedUrl url = new ObsSignedUrl();
                url.setFileId(entity.getId());
                url.setExist(true);
                return url;
            }
        }
    }

    @Override
    public ObsSignedUrl obsGet(Long id) {
        T entity = select(id);
        if(entity != null && webDfsService.exist(webDfsService.dfsFile(entity.getPath()))){
            return obsService.signUrlGet(entity.getPath());
        }
        return ObsSignedUrl.notExist();
    }
}
